﻿<!DOCTYPE html>
<html>
<head>
  <title>Force Directed Layout</title>
  <!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
  <meta name="description" content="Interactive demonstration of physics layout features by the ForceDirectedLayout class." />
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <script src="../release/go.js"></script>
  <script src="../assets/js/goSamples.js"></script>  <!-- this is only for the GoJS Samples framework -->
  <script id="code">
    // define a custom ForceDirectedLayout for this sample
    function DemoForceDirectedLayout() {
      go.ForceDirectedLayout.call(this);
    }
    go.Diagram.inherit(DemoForceDirectedLayout, go.ForceDirectedLayout);

    // Override the makeNetwork method to also initialize
    // ForceDirectedVertex.isFixed from the corresponding Node.isSelected.
    DemoForceDirectedLayout.prototype.makeNetwork = function(coll) {
      // call base method for standard behavior
      var net = go.ForceDirectedLayout.prototype.makeNetwork.call(this, coll);
      net.vertexes.each(function(vertex) {
        var node = vertex.node;
        if (node !== null) vertex.isFixed = node.isSelected;
      });
      return net;
    };
    // end DemoForceDirectedLayout class


    function init() {
      if (window.goSamples) goSamples();  // init for these samples -- you don't need to call this
      var $ = go.GraphObject.make;  // for conciseness in defining templates

      myDiagram =
        $(go.Diagram, "myDiagramDiv",  // must be the ID or reference to div
          {
            initialAutoScale: go.Diagram.Uniform,  // zoom to make everything fit in the viewport
            layout: new DemoForceDirectedLayout()  // use custom layout
            // other Layout properties are set by the layout function, defined below
          });

      // define the Node template
      myDiagram.nodeTemplate =
        $(go.Node, "Spot",
          // make sure the Node.location is different from the Node.position
          { locationSpot: go.Spot.Center },
          new go.Binding("text", "text"),  // for sorting
          $(go.Shape, "Ellipse",
            {
              fill: "lightgray",
              stroke: null,
              desiredSize: new go.Size(30, 30)
            },
            new go.Binding("fill", "fill")),
          $(go.TextBlock,
            new go.Binding("text", "text"))
        );

      // define the Link template
      myDiagram.linkTemplate =
        $(go.Link,
          { selectable: false },
          $(go.Shape,
            { strokeWidth: 3, stroke: "#333" }));

      // generate a tree using the default values
      rebuildGraph();
    }

    function rebuildGraph() {
      var minNodes = document.getElementById("minNodes").value;
      minNodes = parseInt(minNodes, 10);

      var maxNodes = document.getElementById("maxNodes").value;
      maxNodes = parseInt(maxNodes, 10);

      var minChil = document.getElementById("minChil").value;
      minChil = parseInt(minChil, 10);

      var maxChil = document.getElementById("maxChil").value;
      maxChil = parseInt(maxChil, 10);

      generateTree(minNodes, maxNodes, minChil, maxChil);
    }

    function generateTree(minNodes, maxNodes, minChil, maxChil) {
      myDiagram.startTransaction("generateTree");
      // replace the diagram's model's nodeDataArray
      generateNodes(minNodes, maxNodes);
      // replace the diagram's model's linkDataArray
      generateLinks(minChil, maxChil);
      // perform a diagram layout with the latest parameters
      layout();
      myDiagram.commitTransaction("generateTree");
    }

    // Creates a random number of randomly colored nodes.
    function generateNodes(min, max) {
      var nodeArray = [];
      if (isNaN(min) || min < 0) min = 0;
      if (isNaN(max) || max < min) max = min;
      var numNodes = Math.floor(Math.random() * (max - min + 1)) + min;
      for (var i = 0; i < numNodes; i++) {
        nodeArray.push({
          key: i,
          text: i.toString(),
          fill: go.Brush.randomColor()
        });
      }

      // randomize the node data
      for (i = 0; i < nodeArray.length; i++) {
        var swap = Math.floor(Math.random() * nodeArray.length);
        var temp = nodeArray[swap];
        nodeArray[swap] = nodeArray[i];
        nodeArray[i] = temp;
      }

      // set the nodeDataArray to this array of objects
      myDiagram.model.nodeDataArray = nodeArray;
    }

    // Takes the random collection of nodes and creates a random tree with them.
    // Respects the minimum and maximum number of links from each node.
    // (The minimum can be disregarded if we run out of nodes to link to)
    function generateLinks(min, max) {
      if (myDiagram.nodes.count < 2) return;
      if (isNaN(min) || min < 1) min = 1;
      if (isNaN(max) || max < min) max = min;
      var linkArray = [];
      // make two Lists of nodes to keep track of where links already exist
      var nit = myDiagram.nodes;
      var nodes = new go.List(/*go.Node*/);
      nodes.addAll(nit);
      var available = new go.List(/*go.Node*/);
      available.addAll(nodes);
      for (var i = 0; i < nodes.length; i++) {
        var next = nodes.get(i);
        available.remove(next)
        var children = Math.floor(Math.random() * (max - min + 1)) + min;
        for (var j = 1; j <= children; j++) {
          if (available.length === 0) break;
          var to = available.get(0);
          available.remove(to);
          // get keys from the Node.text strings
          var nextKey = parseInt(next.text, 10);
          var toKey = parseInt(to.text, 10);
          linkArray.push({ from: nextKey, to: toKey });
        }
      }
      myDiagram.model.linkDataArray = linkArray;
    }

    // Update the layout from the controls.
    // Changing the properties will invalidate the layout.
    function layout() {
      myDiagram.startTransaction("changed Layout");
      var lay = myDiagram.layout;

      var maxIter = document.getElementById("maxIter").value;
      maxIter = parseInt(maxIter, 10);
      lay.maxIterations = maxIter;

      var epsilon = document.getElementById("epsilon").value;
      epsilon = parseFloat(epsilon, 10);
      lay.epsilonDistance = epsilon;

      var infinity = document.getElementById("infinity").value;
      infinity = parseFloat(infinity, 10);
      lay.infinityDistance = infinity;

      var arrangement = document.getElementById("arrangement").value;
      var arrangementSpacing = new go.Size();
      arrangement = arrangement.split(" ", 2);
      arrangementSpacing.width = parseFloat(arrangement[0], 10);
      arrangementSpacing.height = parseFloat(arrangement[1], 10);
      lay.arrangementSpacing = arrangementSpacing;

      var charge = document.getElementById("charge").value;
      charge = parseFloat(charge, 10);
      lay.defaultElectricalCharge = charge;

      var mass = document.getElementById("mass").value;
      mass = parseFloat(mass, 10);
      lay.defaultGravitationalMass = mass;

      var stiffness = document.getElementById("stiffness").value;
      stiffness = parseFloat(stiffness, 10);
      lay.defaultSpringStiffness = stiffness;

      var length = document.getElementById("length").value;
      length = parseFloat(length, 10);
      lay.defaultSpringLength = length;

      myDiagram.commitTransaction("changed Layout");
    }
  </script>
</head>
<body onload="init()">
<div id="sample">
  <div style="margin-bottom: 5px; padding: 5px; background-color: aliceblue">
    <span style="display: inline-block; vertical-align: top; padding: 5px">
      <b>New Tree</b><br />
      MinNodes: <input type="text" size="3" id="minNodes" value="20" /><br />
      MaxNodes: <input type="text" size="3" id="maxNodes" value="100" /><br />
      MinChildren: <input type="text" size="3" id="minChil" value="1" /><br />
      MaxChildren: <input type="text" size="3" id="maxChil" value="10" /><br />
      <button type="button" onclick="rebuildGraph()">Generate Tree</button>
    </span>
    <span style="display: inline-block; vertical-align: top; padding: 5px">
      <b>ForceDirectedLayout Properties</b><br />
      Max Iterations: <input type="text" size="5" id="maxIter" value="100"  onchange="layout()" /><br />
      Epsilon: <input type="text" size="5" id="epsilon" value="1"  onchange="layout()" /><br />
      Infinity: <input type="text" size="5" id="infinity" value="1000"  onchange="layout()" /><br />
      ArrangementSpacing: <input type="text" size="8" id="arrangement" value="100 100"  onchange="layout()" /><br />
    </span>
    <span style="display: inline-block; vertical-align: top; padding: 5px">
      <b>Vertex Properties</b><br />
      Electrical Charge: <input type="text" size="5" id="charge" value="150"  onchange="layout()" /><br />
      Gravitational Mass: <input type="text" size="5" id="mass" value="0"  onchange="layout()" /><br />
    </span>
    <span style="display: inline-block; vertical-align: top; padding: 5px">
      <b>Edge Properties</b><br />
      Spring Stiffness: <input type="text" size="5" id="stiffness" value="0.05"  onchange="layout()" /><br />
      Spring Length: <input type="text" size="5" id="length" value="50"  onchange="layout()" /><br />
    </span>
  </div>
  <div id="myDiagramDiv" style="background: white; border: solid 1px black; width: 100%; height: 500px"></div>
  <p>
    For information on <b>ForceDirectedLayout</b> and its properties, see the <a>ForceDirectedLayout</a> documentation page.
  </p>
</div>
</body>
</html>